跳到主要内容

Promise A+规范的实现

· 阅读需 18 分钟
Oxygen

Promise/A+

https://promisesaplus.com/#terminology

Promise 对象

  • 一个对象或者函数,并且带有then方法
  • 一个当前状态state,可以是pendingfulfilledrejected,且fulfilledrejected不可被改变
  • 一个resolvevalue,可以是任何 JS 值的形式
  • 一个执行reject的原因reason

then 方法

newPromise = promise.then(onFulfilled, onRejected)

then方法接收两个参数,且必须都是函数,否则忽略该参数

  • onFulfilled:当前 Promise 对象的状态变成fulfilled以后执行,并且将value作为第一个参数,并且只允许执行一次
  • onRejected:当前 Promise 对象的状态变成rejected以后执行,并且将reason作为第一个参数,并且只允许执行一次
  • onFulfilledonRejected必须在当前宏任务执行完以后才能执行,也就是异步的需求
  • then执行完返回一个新的 Promise 对象newPromise
    • 如果onFulfilled或者onRejected返回新的值x,则执行下文的方法Resolve(newPromise, x)
    • 如果onFulfilled或者onRejected执行抛出异常epromise2也必须reject(e)
    • 如果onFulfilled不是函数(未提供),则当promise状态变成fulfilled的时候,newPromise内部状态也要变成fulfilled并以promise内部的value执行resolve;同理onRejected也是
  • 同一个 Promise 对象的then方法可能调用多次
    • 并且各自onFulfilled或者onRejected回调必须按照调用then的顺序依次执行

Resolve(promise, x)

Resolve(promise, x)这里是描述then执行以后的算法判断,也就是then的反复执行过程,这是一个特殊的内部方法,是处理then链式执行的算法;

  • 如果promise === x,直接reject promise并返回TypeError的错误;
  • 如果x是一个 Promise 对象,则当前 Promise 对象promise的状态必须和x同步;
  • 如果x是一个对象或者函数:
    • 判断其是否具有then方法;
    • 如果没有,则reject
    • 如果具有then,则使用x作为then内部的this执行then,第一个参数传递resolvePromise,第二个参数传递rejectPromise
      • resolvePromise被传递参数y调用的时候,执行Resolve(promise, y)
      • rejectPromise被传递参数r调用的时候,则promisereject
      • 如果resolvePromiserejectPromise都被调用了,或者被多次调用,首次调用优先执行,后续被忽略;
      • 如果执行then抛出异常,当resolvePromise或者rejectPromise被调用了,则忽略;否则reject顶层的promise
  • 如果x不满足上述条件,promise直接fulfilled

实现

如果按照 promise-aplus 的算法慢慢摸索就可以直接实现一个完整的 Promise

初始化

Promise 对象在初始化以后,具有以上提到的valuestate以及reason等状态值

class APromise {
constructor(fn) {
if (typeof fn !== 'function') {
throw new TypeError('init parameter must be a function');
}

this.state = 'pending';
this.value = null;
this.reason = null;

try {
fn(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}

resolve(value) {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
});
}

reject(reason) {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
}
});
}
}

then

在上述代码的基础上,添加then方法,因为then方法可能被调用多次,所以我们需要额外定义两个队列保存在 Promise 状态发生变化以后的回调函数onFulfilledonRejected,这里做一个参数非函数类型的简单化处理,这样只需要考虑函数的回调形式,毕竟日常开发也不会传递其他类型的参数

class APromise {
constructor(fn) {
this.state = 'pending';
this.value = null;
this.reason = null;
this.fulfilledQue = [];
this.rejectedQue = [];

fn(this.resolve, this.reject);
}

then = (onFulfilled, onRejected) => {
this.fulfilledQue.push(
typeof onFulfilled === 'function' ? onFulfilled : value => value,
);

this.rejectedQue.push(
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
},
);
};
}

然后then方法返回一个新的 Promise 对象newPromise,并且这个新的 Promise 对象和当前 Promise 的状态保持同步,所以在then内部需要判断当前 Promise 的状态来进行不同的处理:

  • 如果是fulfilled状态,则表明当前 Promise 已经执行了resolve,所以应该以当前 Promise 的value异步执行onFulfilled,并把当前 Promise 的value传递下去;rejected状态同理
  • 而如果当前 Promise 状态是pending,那么应该将onFulfilledonRejected推到回调函数中去,等待状态改变的时候自动调用
then = (onFulfilled, onRejected) => {
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
};
const newPromise = new APromise((resolve, reject) => {
if (this.state === 'fulfilled') {
// https://promisesaplus.com/#point-43
// 这里直接异步执行回调函数,保证返回的 Promise 状态和当前 Promise 状态一致
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}

if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}

// 如果当前 Promise 状态
if (this.state === 'pending') {
this.fulfilledQue.push(value => {
try {
const x = onFulfilled(value);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});

this.rejectedQue.push(reason => {
try {
const x = onRejected(reason);
this.resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}
});

return newPromise;
};

然后我们需要在 Promise 状态发生改变的时候调用onFulfilledonRejected,可以在原来的resolvereject基础上进行补充,这里使用setTimeout模拟异步实现的方式

resolve = value => {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.fulfilledQue.forEach(cb => {
cb(this.value);
});
}
});
};

reject = reason => {
setTimeout(() => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.rejectedQue.forEach(cb => {
cb(this.reason);
});
}
});
};

resolvePromise

resolvePromise的实现相对简单一点,直接按照规范定义一步一步来即可

resolvePromise = (promise, x, resolve, reject) => {
// https://promisesaplus.com/#point-48
// 如果then提供的onFulfilled或者onRejected函数执行返回的值和then返回的是同一个promise,会导致下文this.resolvePromise重复调用,形成死循环
if (promise === x) {
reject(
new TypeError(
'then must return a different promise with fulfilled callback',
),
);
}

// https://promisesaplus.com/#point-49
// 如果是一个 Promise,需要判断其状态,并同步到后续的 Promise
if (x instanceof APromise) {
// https://promisesaplus.com/#point-50
if (x.state === 'pending') {
x.then(
value => {
this.resolvePromise(promise, value, resolve, reject);
},
reason => {
reject(reason);
},
);
} else {
// https://promisesaplus.com/#point-51
x.then(resolve, reject);
}
} else if (typeof x === 'function' || (typeof x === 'object' && x !== null)) {
// https://promisesaplus.com/#point-53
// thenable 函数
let then;
// https://promisesaplus.com/#point-59
// 保证x.then提供的回调函数只会被执行一次
let hasBeenResolved = false;
try {
then = x.then;
// https://promisesaplus.com/#point-56
if (typeof then === 'function') {
then.call(
x,
y => {
if (!hasBeenResolved) {
hasBeenResolved = true;
// https://promisesaplus.com/#point-57
this.resolvePromise(promise, y, resolve, reject);
}
},
r => {
if (!hasBeenResolved) {
hasBeenResolved = true;
reject(r);
}
},
);
} else {
// https://promisesaplus.com/#point-63
resolve(x);
}
} catch (e) {
// https://promisesaplus.com/#point-60
if (!hasBeenResolved) {
reject(e);
}
}
} else {
// https://promisesaplus.com/#point-64
resolve(x);
}
};

测试

使用 promise-aplus 规范提供的promises-tests进行测试,在原有代码上暴露以下入口方法,然后使用 cjs 语法导出即可

/**
* 测试入口
*/
APromise.defer = APromise.deferred = function() {
let dfd = {};
dfd.promise = new APromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};

执行promises-aplus-tests,可以看到完美通过 872 个测试用例。